/******************************************************************************
*
* This file is part of FreeTrack (http://code.google.com/p/qtfreetrack).
* BY Willy Gardiol (willy@gardiol.org)
*
* FreeTrack is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FreeTrack is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Foobar.  If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
#include "oauthmanager.h"
#include <QNetworkRequest>
#include "configwidget.h"
#include "ftlabel.h"
#include "ftpushbutton.h"

#ifdef Q_OS_SYMBIAN
#include <hash.h>
#include <imcvcodc.h>
#endif

extern ConfigWidget* conf;

/*
 when updating to support more OAuth sites, create a "settings" class and keep this
 class as general as possible
*/

#define BREADCRUMBS_KEY "jlcAwUYu9yx1aGZPWmnR"
#define BREADCRUMBS_SECRET "1isgJKJh9uNkB5W3x5AWxVtZQOP1DrOJiimcXImj"
#define BREADCRUMBS_URL "http://api.gobreadcrumbs.com"

OauthManager::OauthManager(QObject *parent) :
    QObject(parent),
    status(NOAUTH),
    baseUrl(BREADCRUMBS_URL),
    consumerSecret(BREADCRUMBS_SECRET),
    consumerKey(BREADCRUMBS_KEY),
    error(false)
{
    // this loop prevents ConfigWidget to call THIS constructor! Use the other instead.
    if ( conf->getBreadcrumbsToken( token, tokenSecret ) )
        status = AUTH;
    network_manager = new QNetworkAccessManager;
    connect( network_manager, SIGNAL(finished(QNetworkReply*)),
             this, SLOT(parseReply(QNetworkReply*)) );
    wait_progress = NULL;
}

OauthManager::OauthManager(QObject *parent, QByteArray t, QByteArray ts) :
    QObject(parent),
    status(NOAUTH),
    baseUrl(BREADCRUMBS_URL),
    consumerSecret(BREADCRUMBS_SECRET),
    consumerKey(BREADCRUMBS_KEY),
    error(false)
{
    token = t;
    tokenSecret = ts;
    if ( (t != "") && ( ts != "" ) )
        status = AUTH;
    network_manager = new QNetworkAccessManager;
    connect( network_manager, SIGNAL(finished(QNetworkReply*)),
             this, SLOT(parseReply(QNetworkReply*)) );
    wait_progress = NULL;
}


OauthManager::~OauthManager()
{
    delete network_manager;
}

void OauthManager::openWait(QString title, QNetworkReply* reply, qint64 size )
{
    QDialog wait_dialog;
    QVBoxLayout wait_layout(&wait_dialog);
#ifdef Q_OS_SYMBIAN
    QAction wait_cancel( tr("Abort..."), &wait_dialog );
#else
    FTPushButton wait_cancel( tr("Abort...") );
#endif
    FTLabel wait_title(title);

    wait_layout.addWidget( &wait_title );
    wait_progress = new QProgressBar();
    wait_progress->setMinimum(0);
    wait_progress->setMaximum(size);
    wait_progress->setValue(0);
    wait_layout.addWidget( wait_progress );

#ifdef Q_OS_SYMBIAN
    wait_cancel.setSoftKeyRole( QAction::NegativeSoftKey );
    wait_dialog.addAction(&wait_cancel);
#else
    wait_layout.addWidget( &wait_cancel );
#endif

#ifdef Q_OS_SYMBIAN
    connect( &wait_cancel, SIGNAL(triggered()), &wait_dialog, SLOT(close()));
#else
    connect( &wait_cancel, SIGNAL(clicked()), &wait_dialog, SLOT(close()));
#endif
    connect( network_manager, SIGNAL(finished(QNetworkReply*)),
                &wait_dialog, SLOT(close()) );

    connect( reply, SIGNAL(bytesWritten(qint64)),
             this, SLOT(updateUpload(qint64)));
    connect( reply, SIGNAL(downloadProgress(qint64,qint64)),
             this, SLOT(updateUpload(qint64)));
    connect( reply, SIGNAL(uploadProgress(qint64,qint64)),
             this, SLOT(updateUpload(qint64)));

    wait_dialog.exec();
    wait_layout.removeWidget(wait_progress);
    delete wait_progress;
    wait_progress = NULL;
}

void OauthManager::updateUpload(qint64 bytesSent)
{
    if ( wait_progress != NULL )
        wait_progress->setValue( wait_progress->value() + bytesSent);
}

bool OauthManager::getRequestToken()
{
    error = false;
    // For the request token we need consumerSecret and consumerKey...
    if ( consumerSecret.isEmpty() || consumerKey.isEmpty() )
    {
        error = true;
        return false;
    }
    status = NOAUTH;

    QString url = baseUrl + "/oauth/request_token";
    token.clear();
    tokenSecret.clear();
    OauthParams ret_params;
    performRequest(url,
                   token,
                   tokenSecret,
                   ret_params,
                   QString("POST"),
                   tr("Requesting authorization..."));
    if ( !error )
    {
        ret_params = processRawReply();
        token = ret_params.value( "oauth_token" );
        tokenSecret = ret_params.value( "oauth_token_secret" );
        status = WAIT_PIN;
    }
    return !error;
}

bool OauthManager::getAccessToken(const QByteArray& pin)
{
    error = false;
    if ( token.isEmpty() ||
         tokenSecret.isEmpty() ||
         status != WAIT_PIN )
    {
        error = true;
        return false;
    }

    QString url = baseUrl + "/oauth/access_token";
    OauthParams params;
    params.insert( "oauth_verifier", pin );

    performRequest(url,
                   token,
                   tokenSecret,
                   params,
                   QString("POST"),
                   tr("Authorizing PIN..."));
    if ( !error )
    {
        OauthParams ret_params = processRawReply();
        status = AUTH;
        token = ret_params.value( "oauth_token" );
        tokenSecret = ret_params.value( "oauth_token_secret" );
    }
    else
    {
        status = NOAUTH;
        token.clear();
        tokenSecret.clear();
    }
    return !error;
}

QByteArray OauthManager::getData( QString url,
                                   const OauthParams& params,
                                   QString title)
{
    error = false;
    if ( status != AUTH )
    {
        error = true;
        return QByteArray();
    }
    performRequest(baseUrl + "/" + url,
                                            token,
                                            tokenSecret,
                                            params,
                                            QString("GET"),
                                            title);
    return rawReply;
}

QByteArray OauthManager::postData( QString url,
                                   const OauthParams& params,
                                   QString title)
{
    error = false;
    if ( status != AUTH )
    {
        error = true;
        return QByteArray();
    }
    performRequest(baseUrl + "/" + url,
                                            token,
                                            tokenSecret,
                                            params,
                                            QString("POST"),
                                            title);
    return rawReply;
}

QByteArray OauthManager::performRequest( const QString &url,
                                          const QByteArray &token,
                                          const QByteArray &tokenSecret,
                                          const OauthParams &params,
                                          QString mode,
                                          QString title)
{
    OauthParams localParams = params;

    // timestamp
    uint time = QDateTime::currentDateTime().toTime_t();
    QByteArray timestamp = QByteArray::number( time );
    // nonce
    QByteArray nonce;
    qsrand( time );
    for (int p = 0; p < 16; p++ )
        nonce += QString("%1").arg( qrand()%10 );

    // Add required parameters...
    localParams.insert("oauth_nonce", nonce);
    localParams.insert("oauth_timestamp", timestamp);
    localParams.insert( "oauth_consumer_key", consumerKey );
    localParams.insert( "oauth_signature_method", "HMAC-SHA1" );
    localParams.insert( "oauth_version", "1.0" );
    localParams.insert( "oauth_token", token );

    // create signature
    error = false;

    QByteArray signature = sign( url,
                                 token,
                                 tokenSecret,
                                 localParams,
                                 mode);
    if ( error )
        return QByteArray();

    // add signature to parameters
    localParams.insert( "oauth_signature", signature );

    QNetworkRequest request;
    QByteArray header;

    if ( mode == "POST" )
    {
        request.setHeader(QNetworkRequest::ContentTypeHeader,
                      "application/x-www-form-urlencoded" );
        header = convertParams( localParams, "=", "&" );
    }
    else if ( mode == "GET" )
    {
        header = "OAuth "+convertParams( localParams, "=\"", "\"," );
        request.setRawHeader( "Authorization", header );
    }

    request.setUrl( QUrl( url ) );

    error = true;
    rawReply.clear();
    if ( conf->getInternetAuthorization() )
    {
        // Setup the request...
        QNetworkReply *reply;

        if ( mode == "POST" )
            reply = network_manager->post( request, header );
        else if ( mode == "GET" )
            reply = network_manager->get( request );

        // start the event loop and wait for the response
        openWait( title, reply, header.size() );
        delete reply;
    }
    return rawReply;
}

QByteArray OauthManager::sign( const QString &url,
                               const QByteArray &token,
                               const QByteArray &tokenSecret,
                               OauthParams& params,
                               QString mode)
{
    // Prepare string to be signed. Start with HTTP method
    QByteArray signatureBase = mode.toAscii();
    // add the URL (%encoded)
    signatureBase.append("&");
    signatureBase.append(url.toAscii().toPercentEncoding());
    // add all the parameters
    signatureBase.append("&");
    signatureBase.append( convertParams( params, "=", "&").toPercentEncoding() );

    QByteArray digest;

#ifdef Q_OS_SYMBIAN
#define OS_SBF 10000
    CSHA1* sha=CSHA1::NewL();
    TBuf8<100> keyVal;
    keyVal.Copy(_L8(QByteArray(consumerSecret.toPercentEncoding() + "&" +
                               tokenSecret.toPercentEncoding()).constData()));

    CHMAC* hmac=CHMAC::NewL(keyVal,sha);
    TBuf8<OS_SBF> baseString;
    int len = signatureBase.size();
    int start = 0;
    while ( (start + OS_SBF ) < len )
    {
        baseString.Copy( _L8( signatureBase.mid( start, OS_SBF ).constData() ) );
        hmac->Hash( baseString );
         start += OS_SBF;
    }
    baseString.Copy( _L8( signatureBase.mid( start ).constData() ) );
    TPtrC8 hashedSig( hmac->Final( baseString ) );

    TImCodecB64 b64enc;
    b64enc.Initialise();
    HBufC8* buf = HBufC8::NewL(hashedSig.Length() * 2);
    buf->Des().Copy(hashedSig);
    TBuf8<512> result;
    // encode your signature into base64 or encoding scheme required
    b64enc.Encode(  buf->Des(),result);
    result.Copy(result.Left(result.Length())); // qui un -1???
    delete hmac;
//    delete buf; crash
//    delete sha; crash
    hmac=NULL;
    digest = QByteArray::fromRawData((char*)result.Ptr(),result.Length());
#else
    digest = QByteArray::fromRawData( "Sorry, HMAC-SHA1 is only supported on Synmbian right now", 10);
#endif

    return digest.toPercentEncoding();
}

QByteArray OauthManager::convertParams(const OauthParams& params,
                                       QString middle,
                                       QString end)
{
    QByteArray param;
    QByteArray return_string;
    Q_FOREACH( param, params.uniqueKeys() )
    {
        QList<QByteArray> values = params.values( param );
        if ( values.size() > 1 ) {
            qSort( values.begin(), values.end() );
        }
        QByteArray value;
        Q_FOREACH ( value, values ) {
            return_string.append( param );
            return_string.append( middle );
            return_string.append( value );
            return_string.append( end );
        }
    }
    // remove the trailing end character (comma or ampersand)
    return_string.chop(1);
    return return_string;
}

bool OauthManager::getToken(QByteArray& retToken, QByteArray& retTokenSecret)
{
    if ( status == AUTH )
    {
        retToken = token;
        retTokenSecret = tokenSecret;
        return true;
    }
    return false;
}

OauthStatus OauthManager::getStatus()
{
    return status;
}

OauthParams OauthManager::processRawReply()
{
    OauthParams requestReply;
    QList<QByteArray> params = rawReply.split( '&' );
    QByteArray param;
    QByteArray key;
    int index;
    // iterate through name=value pairs
    Q_FOREACH ( param, params )
    {
        index = param.indexOf( '=' );
        // key is on the left
        key = param.left( index );
        // value is on the right
        requestReply.insert( key , param.right( param.length() - index - 1 ) );
    }
    return requestReply;
}

void OauthManager::parseReply(QNetworkReply *reply)
{
    int code =  reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt();
    rawReply.clear();
    error = true;
    if ( (code >= 200)&&(code < 300) )
    {
        rawReply = reply->readAll();
        error = false;
    }
/*    else
    {
        FTD << "Error reply received (" << code << ")";
        FTD << "Error data might help:" << reply->readAll();
    }*/
    reply->close();
}

bool OauthManager::getError()
{
    return error;
}

QString OauthManager::getAuthUrl()
{
    if ( status == WAIT_PIN )
    {
        QString url = baseUrl+"/oauth/authorize?oauth_token="+token;
       return url;
   }
    return "";
}
